剛開始看不懂那些亂碼時,真的很痛苦。
Regular Expression常簡寫regexp
,也是Ruby
內的一個Class
。
2.7.3 :001 > Regexp.is_a? Class
=> true
資料檢索非常好用,查找或替換資料內特定內容,規範輸入內容格式,這三點為常見用途。
2.7.3 :025 > ("A".."Z").to_a.join.scan(/[AHY]/)
=> ["A", "H", "Y"]
#掃描"A","H","Y"這三個字元,如果沒有[],等於是要找"AHY"這段。
2.7.3 :034 > "hello".gsub(/[aeiou]/, "ahy")
=> "hahyllahy"
#把這五個母音,替換成"ahy"。
2.7.3 :248 > "abc".match(/./)
=> #<MatchData "a">
2.7.3 :249 > "abc".match(/../)
=> #<MatchData "ab">
2.7.3 :250 > "abc".match(/.../)
=> #<MatchData "abc">
#查找符合資料。
.
是代表任何一個字。幾個.
幾個字,所以一段最簡單的表達式/./
就完成了。
先來認識Regexp.Class
Ruby中Regexp
中通常由/ /
包裝。
2.7.3 :004 > /ASH/.class
=> Regexp
2.7.3 :007 > %r{123}.class
=> Regexp
2.7.3 :011 > %r{123}
=> /123/
%r
才是重點,後面要用{}
,[]
,()
都可以形成regexp
。
所以基本上看到/ /
與%r
包住的code時,就要微笑著仔細解讀一下了。
字串可以直接成為Regexp
物件,一樣是new
出來,但是這部分我們先跳過。
認識WTF的四個主要修飾符(選項:optional)
i
:ignorecaes,忽略大小寫。
2.7.3 :046 > "abcdefghijklmnopqrstuvwxyz".scan(/[WTF]/)
=> []
2.7.3 :047 > "abcdefghijklmnopqrstuvwxyz".scan(/[WTF]/i)
=> ["f", "t", "w"]
m
:multiline。
2.7.3 :237"> "a#123
2.7.3 :238"> opi[b".match(/a.*b/)
=> nil
2.7.3 :239"> "a#123
2.7.3 :240"> opi[b".match(/a.*b/m)
=> #<MatchData "a#123\nopi[b">
這邊看不懂/a.*b/先不要理會,m
直接點說明是允不允許檢測資料內有沒有換行\n
。
但應該要理解為,匹配多行,把換行字符\n
識別為正常字符。
2.7.3 :252"> "abc
2.7.3 :253"> def"
=> "abc\ndef"
比較特別的是.
可以在m模式下匹配換行。
2.7.3 :285"> "a
2.7.3 :286"> b".match(/.../)
=> nil
2.7.3 :287"> "a
2.7.3 :288"> b".match(/.../m)
=> #<MatchData "a\nb">
x
:extended。
2.7.3 :280 > "abc".match(/. . ./)
=> nil
2.7.3 :281 > "abc".match(/. . ./x)
=> #<MatchData "abc">
忽略掉空格,註解也可以忽略。
2.7.3 :289/> "abc".match(/
2.7.3 :290/> . #無意義的註釋
2.7.3 :291/> . #繼續無意義
2.7.3 :292/> . #為了示範的註釋
2.7.3 :293/> /x)
=> #<MatchData "abc">
2.7.3 :294/> "abc".match(/
2.7.3 :295/> . #無意義的註釋
2.7.3 :296/> . #繼續無意義
2.7.3 :297/> . #為了示範的註釋
2.7.3 :298/> /)
=> nil
x
使用上還有一些細節,這邊先認識這樣。
o
這是大概最燒腦的部分,這邊只有簡單解釋,想詳細暸解請前往下方引文原網址。
the go-to source for regular expressions:https://www.regular-expressions.info/ruby.html
/o causes any #{…} substitutions in a particular regex literal to be performed just once, the first time it is evaluated. Otherwise, the substitutions will be performed every time the literal generates a Regexp object.
最簡單的說法是,有o
,編譯#{}
只執行一次保持固定,沒有o
會重複執行。
表達式在能匹配前,會有兩個步驟,確認/abc/
內abc
(字串),再去編譯/abc/
。編譯完成後就會進行緩存。
2.7.3 :305 > /abc/.object_id
=> 1100
2.7.3 :306 > /abc/.object_id
=> 1120
2.7.3 :307 > /abc/.object_id
=> 1140
而在我們需要跑循環時,如果是元字串(source)是不變的,為了避免重新編譯緩存(為了增加效率),表達式不會去重新編譯,只會記住第一次的結果
2.7.3 :308 > ["a", "b", "c"].map{|x| /x/.object_id}
=> [1160, 1160, 1160]
但如果使用了#{}
。
2.7.3 :310 > var = "abc"
=> "abc"
2.7.3 :312 > ["a", "b", "c"].map{|x| /#{x}/.object_id}
=> [1200, 1220, 1240] #會重新編譯所以位置不同。
2.7.3 :313 > ["a", "b", "c"].map{|x| /#{x}/o.object_id}
=> [1260, 1260, 1260] #有o之後就不會進行重新編譯。
上面部分只是示範特性。o
必須符合狀況使用,最簡單的就是如果元字串會改變,就不適合使用。
def test_regexp(string)
regexp = [".....", "....", "...", "..", "."][rand(5)]
#方法內,這行會重複執行。
string.map {|str| str.match(/#{regexp}/)}
end
2.7.3 :370 > test_regexp(["abc", "dfg", "asd"])
=> [#<MatchData "abc">, #<MatchData "dfg">, #<MatchData "asd">]
2.7.3 :371 > test_regexp(["abc", "dfg", "asd"])
=> [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :372 > test_regexp(["abc", "dfg", "asd"])
=> [nil, nil, nil] #可能就是rand到`.....`或`....`。
可以看到我們表達式,假設是希望[".....", "....", "...", "..", "."]
隨機選一個,所以三次執行結果都不同。
使用o
。
def test_regexp(string)
regexp = [".....", "....", "...", "..", "."][rand(5)]
#方法內,這行會重複執行。
string.map {|str| str.match(/#{regexp}/o)}
end
2.7.3 :006 > test_regexp(["abc", "dfg", "asd"])
=> [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :007 > test_regexp(["abc", "dfg", "asd"])
=> [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :008 > test_regexp(["abc", "dfg", "asd"])
=> [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :009 > test_regexp(["abc", "dfg", "asd"])
=> [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
不管幾次,都只記住第一次編譯出來的/./
,那這就不是我們要的了。
new
的差異。
2.7.3 :018 > Regexp.new("abc")
=> /abc/
2.7.3 :019 > Regexp.new("abc", Regexp::IGNORECASE)
=> /abc/i
2.7.3 :020 > Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE | Regexp::EXTENDED)
=> /abc/mix
用new
建構表達式,感覺就像使用method
一樣,帶入參數,建構出你要的正規表達式。
2.7.3 :023 > string = "abc"
=> "abc"
2.7.3 :024 > Regexp.new(string)
=> /abc/
2.7.3 :026 > start = "abcd"
=> "abcd"
2.7.3 :027 > finish = "wxyz"
=> "wxyz"
2.7.3 :028 > Regexp.new(start + ".+" + finish + "\.")
=> /abcd.+wxyz./
2.7.3 :030 > /#{start}.+#{finish}\./
=> /abcd.+wxyz\./
直接以//
建構,感覺上就等於直接在寫一個表達式,但其實都可以,實際上兩個寫法看自己喜歡。反而//
寫法還有一個好處。
2.7.3 :050 > /.../i
=> /.../i
2.7.3 :053 > Regexp.new(/abc/, i)
Traceback (most recent call last):
(irb):53:in <main>': undefined local variable or method `i' for main:Object (NameError)
2.7.3 :003 > Regexp.new("abc", Regexp::IGNORECASE)
=> /abc/i
要帶選項,直接/.../i
還比較方便。可是當不想被重構,或是你今天是要讓使用者輸入的值變成正規表達式,還是用new
比較好。
2.7.3 :055 > one = /abc/
=> /abc/
2.7.3 :056 > two = /#{one}/
=> /(?-mix:abc)/
2.7.3 :057 > three = Regexp.new(one)
=> /abc/
2.7.3 :058 > one == two
=> false
2.7.3 :059 > one == three
=> true
(?-mix)
是代表剛剛的介紹過三個選項i、m、x暫時關閉,下篇會提到,當然我們要檢測的真的就是/abc/
那結果論沒差,但實際上兩者還是不同。寫在//
的會進行轉譯,寫在Regexp.new( )
裡的不會進行轉譯,依照你給的參數進行組合。
明天會開始介紹,那些令人看不懂的符號
,但如果理解今天的部分,會發現明天說的非常簡單,就是要死命記憶而已= =+。
今天的leetcode125. Valid Palindrome
題目連結: https://leetcode.com/problems/valid-palindrome/
題目重點: 經典正規表達式解題。
# @param {String} s
# @return {Boolean}
def is_palindrome(s)
s.downcase.gsub(/[^0-9a-z]/, '') == s.downcase.gsub(/[^0-9a-z]/, '').reverse
end
2.7.3 :030 > s = "A man, a plan, a canal: Panama"
=> "A man, a plan, a canal: Panama"
2.7.3 :031 > s = s.downcase.gsub(/[^0-9a-z]/, '')
=> "amanaplanacanalpanama"
2.7.3 :032 > y = s.reverse
=> "amanaplanacanalpanama"
2.7.3 :033 > s == y
=> true